﻿using System;
using System.IO;
using System.Security.Cryptography;

namespace qip_history_synchronizer.Pop3
{
    /// <summary>
    /// Pop3 Class do handling all of the network communication.
    /// </summary>
    public class Pop3 : System.Net.Sockets.TcpClient
    {

        /// <summary>
        /// Connects to a pop3 server with a specified servername, username, and password.
        /// </summary>
        /// <param name="server">The DNS name of the server to connect to.</param>
        /// <param name="username">The user name for the pop3 account to check</param>
        /// <param name="password">The password for the pop3 account to check</param>
        public void Connect(string server, string username, string password)
        {
            string message;
            string response;

            // Connect to the server on port 110
            Connect(server, 995);
            response = Response();
            CheckResponse(response);

            // Write the username out and check the response
            message = "USER " + username + "\r\n";
            Write(message);

            response = Response();
            CheckResponse(response);

            // Write the password out and check the response
            message = "PASS " + password + "\r\n";
            Write(message);

            response = Response();
            CheckResponse(response);
        }

        /// <summary>
        /// Disconnects from the pop3 Server.
        /// </summary>
        public void Disconnect()
        {
            string message;
            string response;

            // Write QUIT out to the server and check the response.
            message = "QUIT\r\n";
            Write(message);

            response = Response();
            CheckResponse(response);

            // Close the socket connection incase the server does not
            this.Close();
        }

        /// <summary>
        /// Checks the response for the pop3 successful command.
        /// </summary>
        /// <param name="resp">The pop3 Server response</param>
        private void CheckResponse(string resp)
        {
            // Checks for the pop3 successful reponse command "+OK"
            if (resp.Substring(0, 3) != "+OK")
            {
                throw new Pop3Exception(resp);
            }
        }

        /// <summary>
        /// A method to get the list of messages from the server.
        /// </summary>
        /// <returns>
        /// An ArrayList of Pop3Message objects is returned.
        /// </returns>
        public System.Collections.ArrayList List()
        {
            string message;
            string response;

            System.Collections.ArrayList retval = new System.Collections.ArrayList();

            //
            // Get the unique message identifiers
            // This allows us to skip messages that we've already downloaded.
            //
            message = "UIDL\r\n";
            Write(message);
            response = Response();
            CheckResponse(response);

            while (true)
            {
                // List of messages is followed by a lin with nothing but a .\r\n
                response = Response();
                if (response == ".\r\n")
                {
                    break;
                }
                else
                {

                    // Initialize the pop3message
                    Pop3Message msg = new Pop3Message();

                    // Split on the ' ' (space)
                    char[] seps = { ' ' };
                    string[] values = response.Split(seps);

                    // First value is the message number
                    msg.number = Int32.Parse(values[0]);

                    // Second value is the UIDL
                    msg.uidl = values[1].Trim();
                    msg.retrieved = false;
                    retval.Add(msg);
                    continue;
                }
            }

            // Loop through every message and get the number of bytes.
            foreach (Pop3Message msg in retval)
            {
                // Send the command to get the number of bytes
                message = "LIST " + msg.number + "\r\n";
                Write(message);

                response = Response();
                CheckResponse(response);

                // Split on the ' ' (space)
                char[] seps = { ' ' };
                string[] values = response.Split(seps);

                // Parse the value to get the bytes and store it in the pop3 object.
                msg.bytes = int.Parse(values[2]);
            }

            return retval;
        }

        /// <summary>
        /// Download a specified pop3 message.
        /// </summary>
        /// <param name="rhs">pop3 message to download</param>
        /// <returns>Returns the object that was downloaded.</returns>
        public Pop3Message Retrieve(Pop3Message rhs)
        {
            string message;
            string response;

            // Initialize a new object to hold the downloaded message.
            Pop3Message msg = new Pop3Message();
            msg.bytes = rhs.bytes;
            msg.number = rhs.number;

            // Write the command to retrieve the message and check the response.
            message = "RETR " + rhs.number + "\r\n";
            Write(message);
            response = Response();
            CheckResponse(response);

            // Flag the message as retrived.
            msg.retrieved = true;
            while (true)
            {
                response = Response();

                // Message ends with a .\r\n on a line by itself
                if (response == ".\r\n")
                {
                    break;
                }
                else
                {
                    // Add to the message body.
                    msg.message += response;
                }
            }

            return msg;
        }

        /// <summary>
        /// Retrieves a pop3 message and writes it out to a supplied file stream.
        /// </summary>
        /// <param name="rhs">Pop3 message to download.</param>
        /// <param name="fstream">File stream to write out to.</param>
        /// <returns>A copy of the message object, without the body attached.</returns>
        public Pop3Message Retrieve(Pop3Message rhs, FileStream fstream)
        {
            string message;
            string response;

            // Text encoding object to get ascii encoded bytes.
            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

            // Initalize a temp variable to hold to message.
            Pop3Message msg = new Pop3Message();
            msg.bytes = rhs.bytes;
            msg.number = rhs.number;

            // Write the command to retrieve the messages and check the response
            message = "RETR " + rhs.number + "\r\n";
            Write(message);
            response = Response();
            CheckResponse(response);

            // Flag the message as downloaded.
            msg.retrieved = true;
            while (true)
            {
                response = Response();
                // Message ends with a .\r\n on a line by itself.
                if (response == ".\r\n")
                {
                    break;
                }
                else
                {
                    // Get the ascii byte[] from the response string and write it into a
                    // the file stream.
                    byte[] bytes = enc.GetBytes(response);
                    fstream.Write(bytes, 0, bytes.Length);
                }
            }

            // Return the message object.
            return msg;
        }

        /// <summary>
        /// Delete a specified message from the pop3 server.
        /// </summary>
        /// <param name="rhs">The message to delete</param>
        public void Delete(Pop3Message rhs)
        {
            string message;
            string response;

            // Write the delete command to the server, and check the response.
            message = "DELE " + rhs.number + "\r\n";
            Write(message);
            response = Response();
            CheckResponse(response);
        }

        /// <summary>
        /// Utility function to write a string to the socket connection.
        /// </summary>
        /// <param name="message">The string to be written</param>
        private void Write(string message)
        {
            // Object to retrieve the byte[] of ascii characters from a string.
            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

            // Initalize the buffer, and get the ascii encoded bytes.
            byte[] WriteBuffer = new byte[1024];
            WriteBuffer = enc.GetBytes(message);

            // Get the base stream and call the underlaying socket write function.
            System.Net.Sockets.NetworkStream stream = GetStream();
            stream.Write(WriteBuffer, 0, WriteBuffer.Length);
        }

        /// <summary>
        /// Utility function to retrieve the response from the pop3 server.
        /// </summary>
        /// <returns>The response string.</returns>
        private string Response()
        {
            // Object to retrieve a byte[] of ascii characters from a string
            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            // StringBuilder object is used for much faster string operations.
            System.Text.StringBuilder sBuilder = new System.Text.StringBuilder();

            // Get the base stream.
            System.Net.Sockets.NetworkStream stream = GetStream();

            // Loop through all of the characters in the read buffer to get only one line
            // at a time.
            int count = 0;
            // Allocate the buffer for the read operations.
            byte[] buff = new byte[2];

            while (true)
            {
                // initialize a buffer.
                buff.Initialize();

                int bytes = stream.Read(buff, 0, 1);
                if (bytes == 1)
                {
                    sBuilder.Append((char)buff[0]);
                    count++;

                    if (buff[0] == '\n')
                        break;

                }
                else if (bytes == 0)
                {
                    // Socket was disconnected.
                    this.Close();
                    return "-ERR Socket was disconnected.";
                }
                else if (bytes < 0)
                {
                    // Socket had a read error.
                    this.Close();
                    return "-ERR Socket encountered a read error.  Disconnected.";
                }
                else
                {
                    break;
                }
            }
            return sBuilder.ToString();
        }

    };

    /// <summary>
    /// Pop3Message object for holding individual messages as they are downloaded.
    /// </summary>
    public class Pop3Message
    {
        public long number;
        public long bytes;
        public string uidl;
        public bool retrieved;
        public string message;
    };

    /// <summary>
    /// Pop3 Exception class.
    /// </summary>
    public class Pop3Exception : System.ApplicationException
    {

        public Pop3Exception(string str) : base(str) { }
    };
}